#include "vm/frame.h"
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <string.h>

#include "userprog/gdt.h"
#include "userprog/pagedir.h"
#include "userprog/tss.h"
#include "filesys/directory.h"
#include "filesys/file.h"
#include "filesys/filesys.h"
#include "threads/flags.h"
#include "threads/init.h"
#include "threads/interrupt.h"
#include "threads/palloc.h"
#include "threads/thread.h"
#include "threads/synch.h"
#include "threads/vaddr.h"
#include "threads/malloc.h"
#include "vm/page.h"
#include "vm/swap.h"

void frame_init(void)
{
	int i;
	lock_init(&frame_lock);
	
	frames = (struct frame *) malloc (frame_cnt*sizeof(struct frame));
	for (i = 0 ; i < frame_cnt ; ++i)
		frames[i].upage = frames[i].t = NULL; 
}

/*
 *  allocate frame
 */
void* frame_allocate(bool with_ZERO/* is zero allocated? */)
{
	lock_acquire(&frame_lock);

	void *kpage = palloc_get_page(PAL_USER | (with_ZERO ? PAL_ZERO : 0 ) );
//	printf("%u\n", (uint32_t)kpage);	
	if(kpage == NULL)
	{
		//////////////////////////////////////////////////////
		struct frame *f = frame_select(); // 	find frame to be evicted
		if(f == NULL){
			lock_release(&frame_lock);
			return NULL;
		}	
	
			//2) find kpage from page
		kpage = pagedir_get_page(f->t->pagedir,f->upage);
		// something wrong happends
		if(kpage == NULL)
		{
			lock_release(&frame_lock);
			return NULL;
		}

		// 3) find corresponding page
		struct page *_p = page_lookup(f->upage,f->t);


//		if(_p == NULL)
//			printf("page is null!!!\n");
		// 4)swap out
//		if failed to swap out, then we cannot make an empty frame
		if (!swap_out(_p) )
		{
			lock_release(&frame_lock);
			return NULL;
		}


		// clear the frame mapping information
		pagedir_clear_page(f->t->pagedir,f->upage);

	}
	if (with_ZERO)
	{
		memset(kpage,0,PGSIZE);
	}
	return kpage;
}

/*
 *  deallocate frame
 */
void frame_deallocate(void *kpage)
{
	if(kpage == NULL)
		return;
	/*
		with kpage, find frame
		 */
	int idx = ((unsigned int)((uint32_t)kpage-(uint32_t)frame_kpage_base))/PGSIZE;
	palloc_free_page(kpage);
	frames[idx].upage= frames[idx].t = NULL;
	lock_release(&frame_lock);
}

/*
 * when exit,
 * look up whole frame table, clean up frames
 */
void frame_clear()
{
	lock_acquire(&frame_lock);
	struct thread *cur = thread_current();

	int i;
	for (i = 0 ; i < frame_cnt ; ++i)
	{
		if (frames[i].t == cur)
			// what happened when upage is NULL???????????????
		{
			frames[i].upage =	frames[i].t = NULL;
		}
	}
	page_deallocate(cur);
	lock_release(&frame_lock);
}

/*
 * insert page to frame
 */
bool frame_insert_page(void *upage, void *kpage, bool writable)
{
	if(kpage == NULL)
		return false;


	int idx = ((unsigned int)((uint32_t)kpage-(uint32_t)frame_kpage_base))/PGSIZE;	

	ASSERT(idx>=0);
 	ASSERT(idx<frame_cnt);

//	bool success = pagedir_set_page( thread_current()->pagedir, upage, kpage, writable);
	bool success = page_insert(upage,kpage,writable);
	// it should not release the lock!
	// Because our source code will deallocate when it return false, it'll gonna be handled at there
	if (!success)
		return false;
	
	frames[idx].upage = upage;
	frames[idx].t = thread_current();
	
	lock_release(&frame_lock);
	return true;
}


/*
 * get frame, eviction
 */
struct frame* frame_select()
{
	static int start = 0;
	int i;

	for (i = start; ( i = i% frame_cnt ) | 1 ; ++i)
	{
        
		if ( frames[i].upage !=NULL){   
		// if we take upage == NULL then race can happened I guess
		/////////////////////////////////////////////////////////////////////////////
			if(!pagedir_is_accessed (frames[i].t->pagedir,frames[i].upage ))
			{
				start = (i+1)%frame_cnt;
				return &frames[i];
			}
			pagedir_set_accessed(frames[i].t->pagedir,frames[i].upage ,false); 
		}
	}
	//not reachable!!
	return NULL;
}

/*
 * from palloc, set cnt, base
 */
void frame_set_init_variable(int cnt,uint32_t base)
{
	frame_cnt= cnt;
	frame_kpage_base = base;        
//	printf("size : %u %u\n", frame_cnt, frame_kpage_base);
}

int get_frame_count(void){
	return frame_cnt;
}

uint32_t get_kpage_base(void){
	return frame_kpage_base;
}
